package com.tencent.cloud.iov.processor.action;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import com.tencent.cloud.iov.action.Action;

import java.io.IOException;
import java.util.List;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;

/**
 * 行为分发类创建器
 *
 * @author showxiao
 * @date 2018/4/18
 */

class ActionDispatcherGenerator {

    private final ProcessingEnvironment mProcessingEnv;

    ActionDispatcherGenerator(ProcessingEnvironment processingEnv) {
        mProcessingEnv = processingEnv;
    }

    /**
     * 生成行为分发类
     *
     * @param classElement
     * @param methodElements
     * @throws IOException
     */
    void generate(TypeElement classElement, List<ExecutableElement> methodElements)
            throws IOException {

        MethodSpec.Builder dispatch = createMethod(classElement);
        addControlFlow(dispatch, methodElements);
        addNotDefinedFlow(dispatch);
        addReturnStatement(dispatch);

        TypeSpec.Builder actionDispatcher = createClass(classElement, dispatch);
        writeToFile(classElement, actionDispatcher);
    }

    /**
     * 方法签名
     *
     * i.e. public static <T> boolean dispatch(Object target, String actionId, T actionData) {}
     *
     * @param classElement
     * @return
     */
    private MethodSpec.Builder createMethod(TypeElement classElement) {
        return MethodSpec.methodBuilder("dispatch")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .returns(boolean.class)
                .addParameter(ClassName.get(classElement.asType()), "target")
                .addParameter(String.class, "actionId")
                .addParameter(TypeVariableName.get("Object..."), "args");
    }

    /**
     * 添加控制流：根据行为 ID 匹配注解对应的方法进行调用
     *
     * i.e. if (actionId == "update_list") { target.act((String) actionData); } else if () {...}
     *
     * @param dispatch
     * @param methodElements
     */
    private void addControlFlow(MethodSpec.Builder dispatch, List<ExecutableElement> methodElements) {
        boolean hasBegun = false;
        for (ExecutableElement methodElement : methodElements) {
            // i.e. if (actionId == "update_list")
            String id = methodElement.getAnnotation(Action.class).value();
            String controlPrefix = !hasBegun ? "if" : "else if";
            String control = "$L (actionId == $S)";

            // i.e. target.act((String) actionData))
            String methodName = methodElement.getSimpleName().toString();
            String statement = "target.$L($L)";

            // 参数列表
            StringBuilder params = getParameterString(methodElement);

            // i.e. if () {} else if () {}
            if (!hasBegun) {
                dispatch.beginControlFlow(control, controlPrefix, id);
                hasBegun = true;
            } else {
                dispatch.nextControlFlow(control, controlPrefix, id);
            }

            dispatch.addStatement(statement, methodName, params.toString());
        }
    }

    /**
     * 生成参数列表
     *
     * i.e.: target.onTextItemClick((String) args[0], (String) args[1]);
     *
     * @param methodElement
     * @return
     */
    private StringBuilder getParameterString(ExecutableElement methodElement) {
        StringBuilder params = new StringBuilder();
        List<? extends VariableElement> parameters = methodElement.getParameters();
        if (parameters != null && !parameters.isEmpty()) {
            boolean isFirstParam = true;
            for (int i = 0; i < parameters.size(); i++) {
                VariableElement parameter = parameters.get(i);
                if (!isFirstParam) {
                    params.append("\n, ");
                }
                isFirstParam = false;

                params.append("(")
                        .append(parameter.asType().toString())
                        .append(") args[")
                        .append(i)
                        .append("]");
            }
        }
        return params;
    }

    /**
     * 添加未定义行为 ID 方法的处理：抛出异常
     *
     * @param dispatch
     */
    private void addNotDefinedFlow(MethodSpec.Builder dispatch) {
        dispatch.nextControlFlow("else")
                .addStatement("return false")
                .endControlFlow();
    }

    /**
     * 返回结果：return true 表示结束 Action 注解的处理，不再向下传递
     *
     * @param dispatch
     */
    private void addReturnStatement(MethodSpec.Builder dispatch) {
        dispatch.addStatement("return true");
    }

    /**
     * 创建 ActionDispatcher 类：当前类名添加 ActionDispatcher 后缀
     *
     * i.e. MineFragmentActionDispatcher
     *
     * @param classElement
     * @param dispatch
     * @return
     */
    private TypeSpec.Builder createClass(TypeElement classElement, MethodSpec.Builder dispatch) {
        String className = ActionHandlerGenerator.getDispatcherClassName(classElement, mProcessingEnv);
        return TypeSpec.classBuilder(className)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(dispatch.build());
    }

    /**
     * 生成 .java 文件
     *
     * @param classElement
     * @param actionDispatcher
     * @throws IOException
     */
    private void writeToFile(TypeElement classElement, TypeSpec.Builder actionDispatcher)
            throws IOException {
        final String packageName = mProcessingEnv.getElementUtils()
                .getPackageOf(classElement).getQualifiedName().toString();
        JavaFile.builder(packageName, actionDispatcher.build())
                .build()
                .writeTo(mProcessingEnv.getFiler());
    }

}
